﻿using System;
using System.IO.Ports;
using System.Text;
using System.Threading;

namespace TMCM
{
  public class TMCM : IDisposable
  {
    #region Delegates

    // Declare the event using EventHandler<T>
    public event EventHandler<NewDataEventArgs> NewData;

    #endregion Delegates

    #region Members

    private Engine.Comport _comport;
    private SerialSettings comSettings = new SerialSettings();

    private bool _canSend = false;
    private bool _comIsInUse = false;
    private bool _testmode = false;
    // Track whether Dispose has been called.
    private bool disposed = false;

    #endregion Members

    #region Constructor/Destructor

    public TMCM() { }

    public TMCM(string name, int baudrate, Parity parity, int dataBits, StopBits stopBits, Handshake handShake)
    {
      comSettings.PortName = name;
      comSettings.BaudRate = baudrate;
      comSettings.Parity = parity;
      comSettings.DataBits = dataBits;
      comSettings.StopBits = stopBits;
      comSettings.Handshake = handShake;

      _comport = new Engine.Comport(name, baudrate, parity, dataBits, stopBits);
      _comport.Handshake = handShake;
    }

    // Use C# destructor syntax for finalization code.
    // This destructor will run only if the Dispose method
    // does not get called.
    // It gives your base class the opportunity to finalize.
    // Do not provide destructors in types derived from this class.
    ~TMCM()
    {
      // Do not re-create Dispose clean-up code here.
      // Calling Dispose(false) is optimal in terms of
      // readability and maintainability.
      Dispose(false);
    }

    #endregion Constructor/Destructor

    #region Constants

    // move to position types
    public enum MVP_TYPE : byte
    {
      ABS = 0x00,           // absolute position
      REL = 0x01,           // relative position
    }

    //motor/banks
    public enum MOT : byte
    {
      _00 = 0x00         // motor 0
    }

    public enum WAIT_TYPE : byte
    {
      Ticks = 0x00,         // Ticks
      POS = 0x01,           // Target position reached
      LIMSW = 0x03
    }

    private enum TMCL_RESULT
    {
      OK = 0,
      NOT_READY = 1,
      CHECKSUM_ERROR = 2
    }

    #endregion Constants

    #region Proprties

    protected SerialSettings ComSettings
    {
      get { return comSettings; }
      set { comSettings = value; }
    }

    public bool IsOpen
    {
      get { return _comport.IsOpen; }
    }

    public bool TestMode
    {
      get { return _testmode; }
      set { _testmode = value; }
    }

    #endregion Proprties

    #region Public Methods

    public virtual void Close()
    {
      if (_testmode) return;

      _canSend = false;
      CloseCom();
    }

    public virtual void Open()
    {
      if (_testmode) return;

      try
      {
        if (_comport == null)
        {
          _comport = new Engine.Comport(comSettings.PortName, comSettings.BaudRate, comSettings.Parity, comSettings.DataBits, comSettings.StopBits);
          _comport.Handshake = comSettings.Handshake;
        }
        else
          CloseCom();

        // open the comport
        _comport.PortName = comSettings.PortName;
        _comport.Open();
        // set the timeouts for reading data
        _comport.ReadTimeout = 2000;
        _canSend = true;
      }
      catch (Exception ex)
      {
        CloseCom();

        System.Console.WriteLine(ex);
        throw;
      }
    }

    public String GetVersionNumber()
    {
      if (_testmode) return "170V1.9";

      WaitForComport();
      ReceiveData replay = sendCommand(0x01, 0x88, 0x00, 0x00, 0);
      return Encoding.GetEncoding(1252).GetString(replay.ByteArray, 1, replay.ByteArray.Length - 1);
    }

    #endregion Public Methods

    #region Private Methods

    private void CloseCom()
    {
      try
      {
        if (_comport != null)

          _comport.Close();
      }
      catch { }
    }

    protected void WaitForComport()
    {
      if (_testmode) return;

      while (_comIsInUse)
      {
        Thread.Sleep(50);
      };
    }

    protected ReceiveData sendCommand(byte address, byte instruction, byte type, byte bank, int pos)
    {
        ReceiveData replay = new ReceiveData();
        if (!_canSend) return replay;
        _comIsInUse = true;
        SendData message = new SendData(address, instruction, type, bank, pos);

        _comport.Write(message.ByteArray, 0, message.ByteArray.Length);
        System.Console.WriteLine("\nSend TargetAddress:{0:G} Type:{1:G} Instruction:{2:G} Value:{3:G} {4}", message.TargetAddress, message.Instruction, message.Type, message.Value, this.ToString());

        Thread.Sleep(100);

        long Timeout = DateTime.Now.Ticks;
        //Wait for result or the 15 sec timeout
        while (GetResult(ref replay) == TMCL_RESULT.NOT_READY && (DateTime.Now.Ticks - Timeout) < 15000000) ;
        _comIsInUse = false;
        OnRaiseNewDataEvent(new NewDataEventArgs(message, replay));
        return replay;
    }

    private TMCL_RESULT GetResult(ref ReceiveData replay)
    {
      byte[] rxMessage = new byte[9];
      //Check if enough bytes can be read
      if (_comport.IsOpen && _comport.BytesToRead > 8)
      {
        //Receive
        _comport.Read(rxMessage, 0, 9);
        replay = new ReceiveData(rxMessage);
        if (!replay.CRC_OK) return TMCL_RESULT.CHECKSUM_ERROR;
        System.Console.WriteLine("Recive TargetAddress:{0:G} Status:{1:G} Instruction:{2:G} Value:{3}", replay.TargetAddress, replay.Status, replay.Instruction, replay.Value);
      }
      else
      {
        Thread.Sleep(50);
        return TMCL_RESULT.NOT_READY;
      }
      return TMCL_RESULT.OK;
    }

    // Wrap event invocations inside a protected virtual method
    // to allow derived classes to override the event invocation behavior
    protected void OnRaiseNewDataEvent(NewDataEventArgs e)
    {
      // Make a temporary copy of the event to avoid possibility of
      // a race condition if the last subscriber unsubscribes
      // immediately after the null check and before the event is raised.
      EventHandler<NewDataEventArgs> handler = NewData;

      // Event will be null if there are no subscribers
      if (handler != null)
      {
        // Use the () operator to raise the event.
        handler(this, e);
      }
    }

    #endregion Private Methods

    #region IDisposable Members

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
      Dispose(true);
      // Take yourself off the Finalization queue
      // to prevent finalization code for this object
      // from executing a second time.
      GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the
    // runtime from inside the finalizer and you should not reference
    // other objects. Only unmanaged resources can be disposed.
    protected virtual void Dispose(bool disposing)
    {
      // Check to see if Dispose has already been called.
      if (!this.disposed)
      {
        // If disposing equals true, dispose all managed
        // and unmanaged resources.
        if (disposing)
        {
          // Dispose managed resources.
        }
        // Release unmanaged resources. If disposing is false,
        // only the following code is executed.
        if (_comport != null)
        {
          if (_comport.IsOpen)
            _comport.Close();

          _comport.Dispose();
        }
        // Note that this is not thread safe.
        // Another thread could start disposing the object
        // after the managed resources are disposed,
        // but before the disposed flag is set to true.
        // If thread safety is necessary, it must be
        // implemented by the client.
      }
      disposed = true;
    }

    #endregion IDisposable Members
  }

  public class SendData
  {
    public int TargetAddress;
    public int Instruction;
    public int Type;
    public int Bank;
    public int Value;
    private byte[] _ByteArray;

    public SendData() { }

    public SendData(int targetAddress, int instruction, int type, int bank, int value)
    {
      TargetAddress = targetAddress;
      Instruction = instruction;
      Type = type;
      Bank = bank;
      Value = value;
    }

    public byte[] ByteArray
    {
      get
      {
        if (_ByteArray == null)
          _ByteArray = GetByteArray();
        return _ByteArray;
      }
    }

    private byte[] GetByteArray()
    {
      byte[] _bytes = new byte[9];
      int iLength = _bytes.Length;

      _bytes[0] = (byte)TargetAddress;
      _bytes[1] = (byte)Instruction;
      _bytes[2] = (byte)Type;
      _bytes[3] = (byte)Bank;
      _bytes[4] = byte.Parse((Value >> 24 & 0xFF).ToString());
      _bytes[5] = byte.Parse((Value >> 16 & 0xFF).ToString());
      _bytes[6] = byte.Parse((Value >> 8 & 0xFF).ToString());
      _bytes[7] = byte.Parse((Value & 0xFF).ToString());
      _bytes[8] = 0;

      //Calculate Checksum
      for (int i = 0; i < iLength - 1; i++)
      {
        _bytes[8] += _bytes[i];
      }

      return _bytes;
    }
  }

  public class ReceiveData
  {
    public int TargetAddress;
    public int Instruction;
    public int Status;
    public int Value;
    public bool CRC_OK;
    public byte[] ByteArray = new byte[9];

    public ReceiveData() { }

    public ReceiveData(byte[] byteArray)
    {
      ByteArray = byteArray;
      byte Checksum;
      int i;
      Checksum = 0;
      for (i = 0; i < 8; i++)
        Checksum += ByteArray[i];
      CRC_OK = (Checksum == ByteArray[8]);

      TargetAddress = ByteArray[1];
      Status = ByteArray[2];
      Instruction = ByteArray[3];
      Value = (ByteArray[4] << 24) | (ByteArray[5] << 16) | (ByteArray[6] << 8) | ByteArray[7];
    }
  }

  // Define a class to hold custom event info
  public class NewDataEventArgs : EventArgs
  {
    public NewDataEventArgs(SendData sendData, ReceiveData receiveData)
    {
      _sendData = sendData;
      _receiveData = receiveData;
    }

    private SendData _sendData;
    private ReceiveData _receiveData;

    public SendData SendData
    {
      get { return _sendData; }
      set { _sendData = value; }
    }

    public ReceiveData ReceiveData
    {
      get { return _receiveData; }
      set { _receiveData = value; }
    }
  }
}